﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Xml.Serialization;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Tomahawk.Runtime.Canvas;
using Tomahawk.Runtime.Canvas.Sprite;
using Tomahawk.Serialization;
using Tomahawk.Runtime.Localization;
using Tomahawk.Runtime.Timeline;
using System.IO;
using Tomahawk.Runtime.Input;
using Microsoft.Xna.Framework.Audio;
using Tomahawk.Runtime.Audio;
using Tomahawk.Serialization.SerialBoost;

namespace Tomahawk.Runtime.LoadingScreens
{
    public class LoadingScreen : IDisposable
    {
        #region Serializable Properties

        [XmlAttribute("BackgroundEnabled")]
        public bool BackgroundEnabled = true;

        [XmlElement("BackgroundColor")]
        public Color BackgroundColor = Color.White;

        [XmlElement("Timeline")]
        public Timeline.Timeline Timeline = new Timeline.Timeline();

        [XmlElement("Controls")]
        public SerializableList<UIControl> Controls = new SerializableList<UIControl>();

        #endregion

        #region Properties

        //graphics device
        private GraphicsDevice graphicsDevice = null;

        [XmlIgnore]
        public GraphicsDevice GraphicsDevice { get { return graphicsDevice; } }

        //content manager
        private ContentManager contentManager = null;

        [XmlIgnore]
        public ContentManager ContentManager { get { return contentManager; } }

        //input manager
        private InputManager inputManager = null;
        [XmlIgnore]
        public InputManager InputManager { get { return inputManager; } }

        //audio manager
        private AudioManager audioManager = null;

        [XmlIgnore]
        public AudioManager AudioManager { get { return audioManager; } }

        //canvas
        private UICanvas canvas = null;

        [XmlIgnore]
        public UICanvas Canvas { get { return canvas; } }

        //spritebatch
        private SpriteBatch spriteBatch = null;

        //screenRectangle
        private Rectangle screenRectangle = Rectangle.Empty;

        //screenshot
        private ResolveTexture2D screenshotTexture = null;

        //message queue
        private Queue<string> messages = new Queue<string>();
        public void PostMessage(string message)
        {
            lock (this)
            {
                messages.Enqueue(message);
            }
        }

        //loading progress
        private int loadingProgress;
        [XmlIgnore]
        public int LoadingProgress
        {
            get
            {
                lock (this)
                {
                    return loadingProgress;
                }
            }

            set
            {
                lock (this)
                {
                    loadingProgress = value;
                }
            }
        }

        //active screen
        private static Object activeScreenLock = new Object();
        private static LoadingScreen activeScreen = null;
        [XmlIgnore]
        public static LoadingScreen ActiveScreen
        {
            get
            {
                lock (activeScreenLock)
                {
                    return activeScreen;
                }
            }
            private set
            {
                lock (activeScreenLock)
                {
                    if (value != null && activeScreen != null)
                    {
                        throw new Exception("There is already a loading screen active");
                    }
                    activeScreen = value;
                }
            }
        }

        //working
        [XmlIgnore]
        public static bool Working
        {
            get
            {
                lock (activeScreenLock)
                {
                    return activeScreen != null;
                }
            }
        }

        private bool loadingDone = false;
        protected bool LoadingDone { get { return loadingDone; } }
        private Thread thread = null;
        public Thread WorkerThread { get { return thread; } }
        #endregion

        #region Initialization
        private Object FindTargetObject(string name)
        {
            return FindControlByName<UIControl>(name);
        }

        public void Initialize()
        {
            graphicsDevice = Engine.Instance.GraphicsDevice;
            canvas = new UICanvas(graphicsDevice);
            contentManager = new ContentManager(Engine.Instance.Services);
            inputManager = Engine.Instance.InputManager;
            audioManager = Engine.Instance.AudioManager;

            foreach (UIControl control in Controls)
            {
                control.Load(contentManager, Engine.Instance.LocalizationManager);
                control.AddToCanvas(canvas);
            }

            Timeline.Initialize();
        }

        private void LocalizeScreen(LocalizationManager localizationManager)
        {
            foreach (UIControl control in Controls)
            {
                control.Localize(contentManager, localizationManager);
            }
        }
        #endregion

        #region Overrideable Events
        /// <summary>
        /// Called when the loading screen receives a meesage from the main thread. This
        /// is called inside the loading screen thread, so within this method you can't 
        /// use anything outside the LoadingScreen
        /// </summary>
        /// <param name="message"></param>
        protected virtual void OnMessageReceived(string message) { }
        /// <summary>
        /// Called when the loading screen is shown. This is called from the main thread, and
        /// any initialization can be done
        /// </summary>
        protected virtual void OnShow() { }
        /// <summary>
        /// Called when the loading screen needs to be updated. This is called inside the 
        /// loading screen thread, so within this method you can't use anything outside
        /// the LoadingScreen
        /// </summary>
        /// <param name="elapsedTime"></param>
        protected virtual void OnUpdate(float elapsedTime) { }

        /// <summary>
        /// Called when the loading screen needs to read the player input. This is called inside the 
        /// loading screen thread, so within this method you can't use anything outside
        /// the LoadingScreen
        /// </summary>
        /// <param name="input"></param>
        protected virtual void OnProcessInput(InputManager input) { }

        /// <summary>
        /// Return false if the loading screen needs to do some work before unloading it
        /// (for example, wait for a button press). This is called inside the loading screen
        /// thread, so within this method you can't use anything outside the LoadingScreen
        /// </summary>
        /// <param name="elapsedTime"></param>
        protected virtual bool IsDone() { return true; }

        /// <summary>
        /// Return false if the loading screen needs to do some work before creating the loading
        /// thread (for example, playing some animations). This is called inside the loading screen
        /// thread, so within this method you can't use anything outside the LoadingScreen
        /// </summary>
        /// <returns></returns>
        protected virtual bool IsReady() { return true; }
        #endregion

        /// <summary>
        /// The loading had finished
        /// </summary>
        public static bool LoadingFinished
        {
            get
            {
                return activeScreen != null ? activeScreen.LoadingDone : true;
            }
        }

        #region Activation
        /// <summary>
        /// Loading delegate. This function is called from a worker thread and is the place to do all the required
        /// loading. While this thread is working, the loading screen takes control of the main thread and is responsible
        /// for rendering the screen (no normal update/draw calls are executed on the engine). When the loading is done
        /// and the loading screen finishes, the normal behaviour is restored
        /// </summary>
        public delegate void DoLoadingDelegate();
        private DoLoadingDelegate loadingDelegate;

        /// <summary>
        /// Loading finished delegate. This function is called prior to restoring the normal update/draw loop.
        /// This can be used to perform post loading operations.
        /// </summary>
        public delegate void LoadingFinishedDelegate();
        private LoadingFinishedDelegate loadingFinishedDelegate;

        public void Show(DoLoadingDelegate loadingDelegate)
        {
            ActiveScreen = this;
            this.firstUpdate = true;
            this.loadingDelegate = loadingDelegate;
            this.loadingFinishedDelegate = null;
            this.loadingDone = false;
            this.thread = null;
            OnShow();
        }

        public void Show(DoLoadingDelegate loadingDelegate, LoadingFinishedDelegate loadingFinishedDelegate)
        {
            ActiveScreen = this;
            this.firstUpdate = true;
            this.loadingDelegate = loadingDelegate;
            this.loadingFinishedDelegate = loadingFinishedDelegate;
            this.loadingDone = false;
            this.thread = null;
            OnShow();
        }
        #endregion

        #region Update
        private bool firstUpdate = true;
        static internal void Update(float elapsedTime)
        {
            lock (activeScreenLock)
            {
                if (activeScreen != null)
                {
                    activeScreen.UpdateScreen(elapsedTime);
                }
            }
        }

        private void UpdateScreen(float elapsedTime)
        {
            if (firstUpdate)
            {
                if (BackgroundEnabled)
                {
                    TakeScreenshot();
                }

                InitializeCanvas();
                LocalizeScreen(Engine.Instance.LocalizationManager);
                canvas.virtualCanvasScale = Engine.Instance.VirtualCanvasScale;
                Timeline.Connect(FindTargetObject);
                firstUpdate = false;
            }

            if (thread == null && IsReady())
            {
                ThreadStart starter = new ThreadStart(DoLoading);
                thread = new Thread(starter);
                thread.Start();
            }

            // Update required managers
            Engine.Instance.AudioManager.AudioEngine.Update();
            Engine.Instance.InputManager.Update(elapsedTime);

            // Update the timeline
            if (Timeline != null)
            {
                Timeline.Update(elapsedTime);
            }

            lock (this)
            {
                // Process messages
                while (messages.Count() > 0)
                {
                    string message = messages.Dequeue();
                    OnMessageReceived(message);
                }

                // Process input
                OnProcessInput(Engine.Instance.InputManager);

                // Update
                OnUpdate(elapsedTime);
            }

            // Check for finalization
            if (loadingDone && IsDone())
            {
                if (loadingFinishedDelegate != null)
                {
                    loadingFinishedDelegate();
                }
            }
        }

        protected T FindControlByName<T>(string name) where T : UIControl
        {
            foreach (UIControl control in Controls)
            {
                T foundControl = control.FindControlByName<T>(name);
                if (foundControl != null)
                {
                    return foundControl;
                }
            }
            return null;
        }
        #endregion

        #region Rendering
        private void InitializeCanvas()
        {
            lock (Engine.ResourceLock)
            {
                spriteBatch = new SpriteBatch(graphicsDevice);

                screenRectangle = new Rectangle(
                    0,
                    0,
                    graphicsDevice.PresentationParameters.BackBufferWidth,
                    graphicsDevice.PresentationParameters.BackBufferHeight
                    );
            }
        }

        private void TakeScreenshot()
        {
            lock (Engine.ResourceLock)
            {
                if (screenshotTexture != null)
                {
                    screenshotTexture.Dispose();
                }

                screenshotTexture = new ResolveTexture2D(
                    graphicsDevice,
                    graphicsDevice.PresentationParameters.BackBufferWidth,
                    graphicsDevice.PresentationParameters.BackBufferHeight,
                    1,
                    graphicsDevice.PresentationParameters.BackBufferFormat
                    );

                graphicsDevice.ResolveBackBuffer(screenshotTexture);
            }
        }

        static internal void Draw()
        {
            lock (activeScreenLock)
            {
                if (activeScreen != null && !activeScreen.firstUpdate)
                {
                    activeScreen.DrawScreen();
                }
            }
        }

        private Color _bgColor;
        private Vector4 vC1, vC2, vC3;

        private void DrawScreen()
        {
            lock (Engine.ResourceLock)
            {
                if (!LoadingFinished)
                {
                    // Clear the backbuffer
                    graphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer | ClearOptions.Target, Color.Black, 0, 0);
                }

                // Render the background
                if (screenshotTexture != null)
                {
                    SpriteEffect effect = canvas.GetEffectAssignedToChannel("Background");
                    spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);

                    if (effect != null)
                    {
                        effect.Begin();
                    }

                    //multiply colors

                    vC1.X = canvas.overlayColor.R / 255;
                    vC1.Y = canvas.overlayColor.G / 255;
                    vC1.Z = canvas.overlayColor.B / 255;
                    vC1.W = canvas.overlayColor.A / 255;

                    vC2.X = this.BackgroundColor.R / 255;
                    vC2.Y = this.BackgroundColor.G / 255;
                    vC2.Z = this.BackgroundColor.B / 255;
                    vC2.W = this.BackgroundColor.A / 255;

                    Vector4.Multiply(ref vC1, ref vC2, out vC3);
                    _bgColor.R = (byte)(vC3.X * 255);
                    _bgColor.G = (byte)(vC3.Y * 255);
                    _bgColor.B = (byte)(vC3.Z * 255);
                    _bgColor.A = (byte)(vC3.W * 255);
                        
                    spriteBatch.Draw(screenshotTexture, screenRectangle, _bgColor);

                    spriteBatch.End();

                    if (effect != null)
                    {
                        effect.End();
                    }
                }

                bool rebuildBatches = false;
                Vector2 scale = Vector2.One;
                foreach (UIControl control in Controls)
                {
                    rebuildBatches |= control.PreRender(ref screenRectangle, ref scale, 0.0f);
                }

                // render the loading screen
                canvas.Render(rebuildBatches);

                if (!LoadingFinished)
                {
                    graphicsDevice.Present();
                }

                // Check for finalization
                if (loadingDone && IsDone())
                {
                    if (loadingFinishedDelegate != null)
                    {
                        loadingFinishedDelegate();
                    }

                    if (screenshotTexture != null)
                    {
                        screenshotTexture.Dispose();
                    }
                    screenshotTexture = null;
                    ActiveScreen = null;
                }
            }
        }
        #endregion

        #region Threading
        private void DoLoading()
        {
            loadingDelegate();
            lock (this)
            {
                loadingDone = true;
            }
        }
        #endregion

        #region Serialization

        public void Serialize<T>(string file) where T : LoadingScreen
        {
            if (!file.EndsWith("c"))
            {
                SerialBoost.ForceAllUseXmlSerializer = true;
                using (FileStream fs = new FileStream(file, FileMode.Create, FileAccess.Write))
                {
                    XmlSerializer x = new XmlSerializer(typeof(T));
                    x.Serialize(fs, this);
                }
                SerialBoost.ForceAllUseXmlSerializer = false;
            }
            else
            {
                throw new NotImplementedException();
            }
        }

        public static T Deserialize<T>(string file) where T : LoadingScreen
        {
            if (!file.EndsWith("c"))
            {
                SerialBoost.ForceAllUseXmlSerializer = true;
                using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
                {
                    XmlSerializer x = new XmlSerializer(typeof(T));
                    object retVal = x.Deserialize(fs);
                    SerialBoost.ForceAllUseXmlSerializer = false;
                    return (T)retVal;
                }
            }
            else
            {
                throw new NotImplementedException();
            }
        }

        #endregion

        #region IDisposable Members
        public void Dispose()
        {
            lock (this)
            {
                if (thread != null)
                {
                    thread.Abort();
                }
            }
        }
        #endregion
    }
}
